﻿using System;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using ColorSpace;
using NVP8;
using WPFMediaKit.DirectShow.MediaPlayers;
using System.Threading;
using VideoClient.VideoConferencing;
using System.ServiceModel;
using DirectShowLib;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace VideoClient
{
    [CallbackBehavior(UseSynchronizationContext = false)]
    public partial class MainWindow : IVideoConferenceServiceCallback
    {
        private readonly Encoder encoder;
        private readonly Decoder decoder;
        private readonly VideoConferenceServiceClient videoClient;
        private byte[] bitmapData;
        private readonly Packet encodedPacket = new Packet();
        private readonly byte[] encodedData = new byte[30000];
        private byte[] decodedData;
        private int timestamp;
        private Timer fpsTimer;
        private byte[] dataToEncode;
        private readonly VPXImage imageToEncode = new VPXImage();
        private readonly VPXImage decodedImage = new VPXImage();
        
        public MainWindow()
        {
            InitializeComponent();

            videoElement.DesiredMediaSubType = MediaSubType.I420;

            encoder = new Encoder();
            encoder.Init(videoElement.DesiredPixelWidth, videoElement.DesiredPixelHeight);
            decoder = new Decoder();
			decoder.Init();

            videoClient = new VideoConferenceServiceClient(new InstanceContext(this), "NetTcpBinding_IVideoConferenceService");
            videoClient.Open();
            videoClient.Subscribe();
            fpsTimer = new Timer(EncodeAndSendLastSample, null, 125, 125);
        }

        private void PropertiesClicked(object sender, EventArgs e)
        {
            videoElement.ShowPropertyPage();
        }

        private Func<VPXImage> getLastSample;

        private void VideoCapture_NewVideoSample(object sender, VideoSampleArgs e)
        {
            if (dataToEncode == null)
                dataToEncode = new byte[e.Width*e.Height*3/2];

            var rawData = new byte[e.BufferLength];
            Marshal.Copy(e.Buffer, rawData, 0, rawData.Length);
            if (e.MediaSubType == MediaSubType.I420)
            {
                getLastSample = () => imageToEncode.Init(rawData);
            }
            else if (e.MediaSubType == MediaSubType.RGB24)
            {
                getLastSample = () =>
                                    {
                                        Converter.FromRGB24ToI420(e.Width, e.Height, rawData, 0, dataToEncode, 0);
                                        return imageToEncode.Init(dataToEncode);
                                    };
            }
            else if (e.MediaSubType == MediaSubType.YUY2)
            {
                getLastSample = () =>
                                    {
                                        Converter.FromYUY2ToI420(e.Width, e.Height, rawData, 0, dataToEncode, 0);
                                        return imageToEncode.Init(dataToEncode);
                                    };
            }
            else
                getLastSample = null;
        }

        private void EncodeAndSendLastSample(object state)
        {
            var handler = getLastSample;
            if (handler == null)
                return;

            //Console.WriteLine(DateTime.UtcNow.Millisecond);
            var image = handler();
            
            var result = encoder.Encode(image, timestamp++);
            if (!result)
                return;
            
            while (encoder.GetEncodedPacket(encodedPacket))
            {
                var frameLength = encodedPacket.WriteRaw(encodedData, 0, encodedData.Length);
                if (frameLength > 0)
                {
                    var frameBuffer = new byte[frameLength];
                    Buffer.BlockCopy(encodedData, 0, frameBuffer, 0, frameLength);
                    videoClient.Publish(frameBuffer);
                }

                Debug.WriteLine("Sent: " + frameLength);
            }
        }

        public void OnData(byte[] data)
        {
            if (!decoder.Decode(data, 0, data.Length))
                return;

            while (decoder.GetNextFrame(decodedImage))
            {
                var width = decodedImage.GetWidth();
                var height = decodedImage.GetHeight();
                if (decodedData == null)
                    decodedData = new byte[width*height*3/2];
                if (bitmapData == null)
                    bitmapData = new byte[width*height*3];

                decodedImage.WriteTo(decodedData, 0);
                Converter.FromI420ToRGB24(width, height, decodedData, 0, bitmapData, 0);
                receivedImage.Dispatcher.Invoke(new ThreadStart(() =>
                                                                    {
                                                                        receivedImage.Source =
                                                                            BitmapSource.Create(width,
                                                                                                height, 96,
                                                                                                96, PixelFormats.Rgb24,
                                                                                                BitmapPalettes.
                                                                                                    Halftone256,
                                                                                                bitmapData,
                                                                                                3 * width);
                                                                    }));
            }
        }
    }
}
